/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.core;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

import com.onegravity.sudoku.solver.HintResource;
import com.onegravity.sudoku.solver.R;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.rules.chaining.ChainHint;

/**
 * A potential position for a value and an "on"/"off" flag.
 * Actually a ({@link Cell}, value, on/off) triplet.
 * <p>
 * Optionally stores a list of parent potentials, and an explanation.
 * The parents are the potentials from which this one was
 * deduced. The full chain can be found from the last item of the chain using
 * the parent references. Hence this class is also used to represent an entire chain.
 */
public class Potential implements Serializable {

	private final static byte VERSION = 1;
	private static final long serialVersionUID = 1517613321520813638L;

	public enum Cause {
        NakedSingle,
        HiddenBlock,
        HiddenRow,
        HiddenColumn,
        HiddenExtraRegion,
        Advanced
    }

    public Cell cell;
    public int value;
    public boolean isOn;
    public List<Potential> parents = new ArrayList<Potential>(1);
    public String explanation;
    public Cause cause;
    public ChainHint nestedChain;

    /**
     * Use this constructor for deserialization only
     */
    private Potential() {}

    public Potential(Cell cell, int value, boolean isOn) {
        this.cell = cell;
        this.value = value;
        this.isOn = isOn;
        this.explanation = null;
        this.cause = null;
        this.nestedChain = null;
    }

    public Potential(Cell cell, int value, boolean isOn, Cause cause, String explanation) {
        this.cell = cell;
        this.value = value;
        this.isOn = isOn;
        this.cause = cause;
        this.explanation = explanation;
        this.nestedChain = null;
    }

    public Potential(Cell cell, int value, boolean isOn, Cause cause, String explanation, ChainHint nestedChain) {
        this.cell = cell;
        this.value = value;
        this.isOn = isOn;
        this.cause = cause;
        this.explanation = explanation;
        this.nestedChain = nestedChain;
    }

    public Potential(Cell cell, int value, boolean isOn, Potential parent, Cause cause, String explanation) {
        this.cell = cell;
        this.value = value;
        this.isOn = isOn;
        this.parents.add(parent);
        this.cause = cause;
        this.explanation = explanation;
        this.nestedChain = null;
    }

    /**
     * We need custom serialization because of StackOverflow exceptions (Serialization of ArrayList)
     * We do this non-recursive...
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
    	ConcurrentLinkedQueue<Potential> potentials = new ConcurrentLinkedQueue<Potential>(); 
    	potentials.add(this);
    	while (!potentials.isEmpty()) {
    		writeObject(potentials.poll(), out, potentials);
    	}
    }

    private void writeObject(Potential potential, ObjectOutputStream out, ConcurrentLinkedQueue<Potential> potentials) throws IOException {
    	out.writeInt(VERSION);
    	out.writeObject(potential.cell);
    	out.writeInt(potential.value);
    	out.writeBoolean(potential.isOn);
    	out.writeObject(potential.explanation);
    	out.writeObject(potential.cause);
    	out.writeObject(potential.nestedChain);
    	int nrOfParents = (potential.parents==null) ? 0 : potential.parents.size(); 
    	out.writeInt(nrOfParents);
    	if (nrOfParents>0) potentials.addAll(potential.parents);
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    	ConcurrentLinkedQueue<Potential> potentials = new ConcurrentLinkedQueue<Potential>(); 
    	potentials.add(this);
    	while (!potentials.isEmpty()) {
    		readObject(potentials.poll(), in, potentials);
    	}
    }
    
    private void readObject(Potential potential, ObjectInputStream in, ConcurrentLinkedQueue<Potential> potentials) throws IOException, ClassNotFoundException {
    	in.readInt();	// skip version because we don't need it at the moment
    	potential.cell = (Cell) in.readObject();
    	potential.value = in.readInt();
    	potential.isOn = in.readBoolean();
    	potential.explanation = (String) in.readObject();
    	potential.cause = (Cause) in.readObject();
    	potential.nestedChain = (ChainHint) in.readObject();
    	int nrOfParents = in.readInt();
    	potential.parents = new ArrayList<Potential>(1);
    	for (int i=0; i<nrOfParents; i++) {
    		Potential parent = new Potential();
    		potential.parents.add(parent);
    	}
		potentials.addAll(potential.parents);
    }
    
    public void off() {
        this.cell.removePotentialValue(this.value);
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Potential)) return false;
        Potential other = (Potential)o;
        return this.cell.equals(other.cell) &&
        	   this.value==other.value &&
        	   this.isOn==other.isOn;
    }

    public boolean equalsIgnoreState(Object o) {
        if (!(o instanceof Potential)) return false;
        Potential other = (Potential)o;
        return this.cell.equals(other.cell) && this.value==other.value;
    }

    @Override
    public int hashCode() {
        return ((cell.getY() * 9 + cell.getX()) * 9 + value) * 2 + (isOn ? 1 : 0);
    }

    @Override
    public String toString() {
        return cell.toString() + "." + value; //$NON-NLS-1$
    }

    public String toWeakString() {
        return HintResource.getString(isOn ? R.string.contains : R.string.doesntcontain, cell.toString(), value);
    }

    public String toStrongString() {
        return HintResource.getString(isOn ? R.string.mustcontain : R.string.cantcontain, cell.toString(), value);
    }

}